/*--------------------------------------------------------
Copyright (c) 2010 Apolo Pena

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------*/
package org.flashalchemy.ezlib.ezlog.console
{
    import org.flashalchemy.ezlib.ezlog.console.skins.MonochromaticBorderSkin;
    import org.flashalchemy.ezlib.ezlog.console.skins.MonochromaticScrollArrowSkin;
    import org.flashalchemy.ezlib.ezlog.console.skins.MonochromaticScrollThumbSkin;

    import flash.events.Event;
    import flash.events.MouseEvent;

    import mx.containers.Panel;
    import mx.controls.TextArea;
    import mx.core.Application;
    import mx.core.DragSource;
    import mx.core.EdgeMetrics;
    import mx.events.DragEvent;
    import mx.events.FlexEvent;
    import mx.logging.ILogger;
    import mx.logging.Log;
    import mx.managers.DragManager;
    import mx.skins.halo.ScrollArrowSkin;
    import mx.skins.halo.ScrollThumbSkin;
    import mx.skins.halo.ScrollTrackSkin;
    import mx.styles.CSSStyleDeclaration;
    import mx.styles.StyleManager;


    /**
     * <b>Description: </b>The User Interface for the <code>EzProgramConsoleTarget</code>, a draggable, resizable, minimizable winodow with a
     * <code>TextArea</code> within it.<p/>
     * <b>Usage:</b>
     *	<pre>
     * 	     var console:EzProgramConsoleTarget = new EzProgramTargetConsole('sample title', 0.6, themeId);
     *       addChild(console);
     * </pre>
     */
    public class EzConsolePanel extends DraggableResizablePanel
    {
        /**
         * Serious Performance issues can occur when a large amount of requests to populate a TextArea are made within a loop,
         * try not to exceed the value of this property.
         * This value is not enforced but can be used as a guide by other objects or classes to reduce cpu strain.
         */
        public static const RECCOMENDED_MAXIMUM_ALLOWABLE_CALLS_PER_LOOP:int = 100;

        /**
         * Event string for when the panel is resized.
         */
        public static const RESIZE_CLICK:String = "resizeClick";

        /**
         * The title used if no title is set for the panel.
         */
        public static const DEFAULT_WINDOW_TITLE:String = "EzLog Debugging Console";

        /**
         * @private
         * A reference to the main flex application.
         */
        private var flexApplication:Application;

        /**
         * @private
         * The component used to display the log output to the screen.
         */
        private var out:TextArea;

        /**
         * @private
         * The 'guide' used to determine the new size that the window should be resized to, this 'guide' can be visual or non visible.
         */
        private var resizeGuide:RectangleGuide;

        private var logger:ILogger = Log.getLogger('org.flashalchemy.ezlib.ezlog.console.EzConsolePanel');

        private function swapSpecialChars(value:String):String
        {
            value.replace('<', '&lt;');
            value.replace('>', '&gt');
            value.replace('&', '&amp');
            return value;
        }

        /**
         * A line break is added to the end of the value passed in, the <code>TextArea</code> display is created and configured
         * (livescrolling = true, editable = false, horizontalScrollPolicy = 'off') if one does not yet exist and the content
         * in the <code>TextArea</code> is autoscrolled if needed.
         * @param value The desired message to be displayed in the log targets that are registered with the Log (aka EzLog).
         */
        public function set output(value:String):void
        {
            // WARNING: dont try this at home kids.... It's ugly.
            if (themeId == EzConsoleThemeValidator.THEME_MONOCHROMATIC)
            {
                if (value.indexOf('[D]') != -1 || value.indexOf('[DEBUG]') != -1)
                {
                    swapSpecialChars(value);
                    out.htmlText += "<font color='#57B31A'>" + value + "</font>\n";
                }
                if (value.indexOf('[D]') == -1 && value.indexOf('[DEBUG]') == -1)
                {
                    // out.htmlText += "<font color='#009900'>" + value + "</font>\n" ;
                }
                if (value.indexOf('[I]') != -1 || value.indexOf('[INFO]') != -1)
                {
                    swapSpecialChars(value);
                    out.htmlText += "<font color='#A8A813'>" + value + "</font>\n";
                }
                if (value.indexOf('[I]') == -1 && value.indexOf('[INFO]') == -1)
                {
                    // out.htmlText += "<font color='#009900'>" + value + "</font>\n";
                }
                if (value.indexOf('[W]') != -1 || value.indexOf('[WARN]') != -1)
                {
                    swapSpecialChars(value);
                    out.htmlText += "<font color='#C17402'>" + value + "</font>\n";
                }
                if (value.indexOf('[W]') == -1 && value.indexOf('[WARN]') == -1)
                {
                    //out.htmlText += "<font color='#009900'>" + value + "</font>\n";
                }
                if (value.indexOf('[E]') != -1 || value.indexOf('[ERROR]') != -1)
                {
                    swapSpecialChars(value);
                    out.htmlText += "<font color='#A90101'>" + value + "</font>\n";
                }
                if (value.indexOf('[E]') == -1 && value.indexOf('[ERROR]') == -1)
                {
                    // out.htmlText += "<font color='#009900'>" + value + "</font>\n";
                }
                if (value.indexOf('[F]') != -1 || value.indexOf('[FATAL]') != -1)
                {
                    swapSpecialChars(value);
                    out.htmlText += "<font color='#9E0C56'>" + value + "</font>\n";
                }
                if (value.indexOf('[F]') == -1 && value.indexOf('[FATAL]') == -1)
                {
                    //out.htmlText += "<font color='#009900'>" + value + "</font>\n";
                }
            }
            else
            {
                out.htmlText += value + "\n";
            }
            out.validateNow();
            out.verticalScrollPosition = out.maxVerticalScrollPosition;
        }

        /**
         * The theme style to use for the UI, this will be either 'default' or 'monochromatic'
         * @return Either 'default' or 'monochromatic'
         */
        public function get themeId():String
        {
            return _themeId;
        }
        /**
         * @private
         *  theme id string
         */
        private var _themeId:String;

        /**
         * Constructor sets the proper events, configurations and references.
         */
        public function EzConsolePanel(title:String = null, alpha:Number = 1.0, themeId:String = EzConsoleThemeValidator.THEME_DEFAULT)
        {
            super();
            this.alpha = alpha;
            (title == null || title == '' ) ? this.title = EzConsolePanel.DEFAULT_WINDOW_TITLE : this.title = title;
            this.title = title;
            addEventListener(Event.OPEN, onTitleBarUiClick);
            addEventListener(Event.CLOSE, onTitleBarUiClick);
            this.verticalScrollPolicy = 'off';
            if (!flexApplication)
            {
                flexApplication = Application(Application.application);
            }
            if (!resizeGuide)
            {
                resizeGuide = new RectangleGuide();
                resizeGuide.width = 0;
                resizeGuide.height = 0;
                resizeGuide.visible = false;
                flexApplication.addChild(resizeGuide);
            }
            flexApplication.addEventListener(DragEvent.DRAG_ENTER, onDragEnter);
            flexApplication.addEventListener(DragEvent.DRAG_DROP, onDragDrop);
            addEventListener(EzConsolePanel.RESIZE_CLICK, onResized);
            addEventListener(FlexEvent.CREATION_COMPLETE, onThisCreationComplete);
            // TODO someday: make a ConsoleTheme class to handle logic like this and make a EZConsoleThemeType class too
            switch (themeId)
            {
                case EzConsoleThemeValidator.THEME_MONOCHROMATIC:
                    _themeId = themeId
                    setThemeAsMonochromatic();
                    break;
                case EzConsoleThemeValidator.THEME_DEFAULT:
                    _themeId = themeId
                    setThemeAsDefault();
                    break;
                default:
                    logger.warn('invalid theme id: ' + themeId + ' The default theme will be used');
                    _themeId = EzConsoleThemeValidator.THEME_DEFAULT;
                    setThemeAsDefault();
            }
        }

        override public function get viewMetrics():EdgeMetrics
        {
            if (themeId == EzConsoleThemeValidator.THEME_DEFAULT)
            {
                return super.viewMetrics;
            }
            else if (themeId == EzConsoleThemeValidator.THEME_MONOCHROMATIC)
            {
                var metrics:EdgeMetrics = super.viewMetrics.clone();
                if (titleBar)
                {
                    metrics.top = titleBar.height;
                }
                if (controlBar)
                {
                    metrics.bottom = controlBar.height
                }
                return metrics;
            }
            return null;
        }

        /**
         * @private
         **/
        private function setThemeAsDefault():void
        {
            this.setStyle('themeColor', 0x458D14);
            if (!out)
            {
                var panelStyle:CSSStyleDeclaration = new CSSStyleDeclaration("EzConsolePanel");
                panelStyle.setStyle('color', 0x000000);
                panelStyle.setStyle("fontWeight", "regular"); // not respected in the titlebar header
                panelStyle.setStyle("fontSize", 9);
                panelStyle.setStyle("letterSpacing", 0);
                panelStyle.setStyle('borderAlpha', alpha);
                if (!StyleManager.getStyleDeclaration('.defaultConsoleTitleStyle'))
                    StyleManager.setStyleDeclaration(".defaultConsoleTitleStyle", panelStyle, true);
                setStyle('styleName', 'defaultConsoleTitleStyle');
                out = new TextArea();
                out.liveScrolling = true;
                out.editable = false;
                out.horizontalScrollPolicy = 'off';
                out.setStyle('backgroundAlpha', 0.0);
                out.setStyle("fontSize", 10);
                out.setStyle("color", 0x000000);
                out.setStyle("letterSpacing", 0);
                out.setStyle("borderColor", 0xFFFFFF);
                out.setStyle("themeColor", StyleManager.getColorName(0x666666));
                var style:CSSStyleDeclaration = new CSSStyleDeclaration("ScrollBar");
                style.setStyle("borderColor", StyleManager.getColorName(0x999999));
                style.setStyle("cornerRadius", 2);
                style.setStyle("downArrowDisabledSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("downArrowDownSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("downArrowOverSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("downArrowUpSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("fillAlphas", [ 0.6, 0.4 ]);
                style.setStyle("fillColors", [ 0xFFFFFF, StyleManager.getColorName("grey")]);
                style.setStyle("highlightAlphas", [ 0.3, 0.0 ]);
                style.setStyle("iconColor", StyleManager.getColorName("black"));
                style.setStyle("thumbDownSkin", mx.skins.halo.ScrollThumbSkin);
                style.setStyle("thumbIcon", undefined);
                style.setStyle("thumbOverSkin", mx.skins.halo.ScrollThumbSkin);
                style.setStyle("thumbUpSkin", mx.skins.halo.ScrollThumbSkin);
                style.setStyle("trackColors", [ 0xFFFFFF, StyleManager.getColorName("grey")]);
                style.setStyle("trackSkin", mx.skins.halo.ScrollTrackSkin);
                style.setStyle("upArrowDisabledSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("upArrowDownSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("upArrowOverSkin", mx.skins.halo.ScrollArrowSkin);
                style.setStyle("upArrowUpSkin", mx.skins.halo.ScrollArrowSkin);
                if (!StyleManager.getStyleDeclaration(".loggerDefaultScrollBarStyle"))
                    StyleManager.setStyleDeclaration(".loggerDefaultScrollBarStyle", style, false);
                out.setStyle("verticalScrollBarStyleName", "loggerDefaultScrollBarStyle");
                addChild(out);
            }
        }

        /**
         * @private
         */
        private function setThemeAsMonochromatic():void
        {
            this.setStyle('themeColor', 0x458D14);
            StyleManager.registerColorName("monochromeGreen", 0x458D14);
            StyleManager.registerColorName("monochromeDarkGreen", 0x244A0B);
            StyleManager.registerColorName("monochromeUltraDarkGreen", 0x1E3C09);
            resizeGuide.lineColor = StyleManager.getColorName("monochromeDarkGreen");
            resizeGuide.fillColor = StyleManager.getColorName("monochromeDarkGreen");
            var panelStyle:CSSStyleDeclaration = new CSSStyleDeclaration("EzConsolePanel");
            panelStyle.setStyle('color', StyleManager.getColorName("monochromeGreen"));
            panelStyle.setStyle("fontWeight", "regular"); // not respected in the titlebar header
            panelStyle.setStyle("fontSize", 10);
            panelStyle.setStyle("letterSpacing", 2);
            panelStyle.setStyle('borderAlpha', alpha);
            if (!StyleManager.getStyleDeclaration(".monochromaticConsoleTitleStyle"))
                StyleManager.setStyleDeclaration(".monochromaticConsoleTitleStyle", panelStyle, true);
            setStyle('styleName', 'monochromaticConsoleTitleStyle');
            out = new TextArea();
            out.liveScrolling = true;
            out.editable = false;
            out.horizontalScrollPolicy = 'off';
            out.setStyle('backgroundAlpha', 0.0);
            out.setStyle("fontSize", 11);
            out.setStyle("letterSpacing", .2);
            out.setStyle("borderColor", StyleManager.getColorName("monochromeGreen"));
            out.setStyle("themeColor", StyleManager.getColorName("monochromeGreen"));
            var scrollStyle:CSSStyleDeclaration = new CSSStyleDeclaration("ScrollBar");
            scrollStyle.setStyle("borderColor", StyleManager.getColorName("monochromeGreen"));
            scrollStyle.setStyle("cornerRadius", 5);
            scrollStyle.setStyle("downArrowDisabledSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("downArrowDownSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("downArrowOverSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("downArrowUpSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("fillAlphas", [ 0.6, 0.4 ]);
            scrollStyle.setStyle("fillColors", [ StyleManager.getColorName("monochromeGreen"), StyleManager.getColorName("monochromeGreen")]);
            scrollStyle.setStyle("highlightAlphas", [ 0.3, 0.0 ]);
            scrollStyle.setStyle("iconColor", StyleManager.getColorName("monochromeGreen"));
            scrollStyle.setStyle("thumbDownSkin", MonochromaticScrollThumbSkin);
            scrollStyle.setStyle("thumbIcon", undefined);
            scrollStyle.setStyle("thumbOverSkin", MonochromaticScrollThumbSkin);
            scrollStyle.setStyle("thumbUpSkin", MonochromaticScrollThumbSkin);
            scrollStyle.setStyle("trackColors", [ StyleManager.getColorName("monochromeGreen"), StyleManager.getColorName("black")]);
            scrollStyle.setStyle("trackSkin", mx.skins.halo.ScrollTrackSkin);
            scrollStyle.setStyle("upArrowDisabledSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("upArrowDownSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("upArrowOverSkin", MonochromaticScrollArrowSkin);
            scrollStyle.setStyle("upArrowUpSkin", MonochromaticScrollArrowSkin);
            StyleManager.setStyleDeclaration(".monochromaticScrollBarStyle", scrollStyle, false);
            out.setStyle("verticalScrollBarStyleName", "monochromaticScrollBarStyle");
            addChild(out);
        }
        /**
         * Override, see code
         */
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            super.updateDisplayList(unscaledWidth, unscaledHeight);
            switch (themeId)
            {
                case EzConsoleThemeValidator.THEME_DEFAULT:
                    createTitleBarUi(unscaledWidth, unscaledHeight);
                    break;
                case EzConsoleThemeValidator.THEME_MONOCHROMATIC:
                    if (getStyle('borderSkin') != MonochromaticBorderSkin)
                    {
                        this.setStyle('borderSkin', MonochromaticBorderSkin); // FYI: 8 pixels of padding on each side
                    }
                    createTitleBarUi(unscaledWidth, unscaledHeight, 0x458D14);
                    resizeIcon.mouseEnabled = false;
                    resizeIcon.buttonMode = true;
                    // Draw resize graphics if not minimzed.				
                    resizeIcon.graphics.clear()
                    if (isMinimized == false)
                    {
                        resizeIcon.graphics.lineStyle(2, 0x458D14, 1.0);
                        resizeIcon.graphics.moveTo(unscaledWidth - 6, unscaledHeight - 1)
                        resizeIcon.graphics.curveTo(unscaledWidth - 3, unscaledHeight - 3, unscaledWidth - 1, unscaledHeight - 6);
                        resizeIcon.graphics.moveTo(unscaledWidth - 6, unscaledHeight - 4)
                        resizeIcon.graphics.curveTo(unscaledWidth - 5, unscaledHeight - 5, unscaledWidth - 4, unscaledHeight - 6);
                    }
                    //resizeIcon.x = 8; // wont work!! the visual changes pos but the hit spot remains in the same location, go figure
                    break;
                default:
                    logger.warn('invalid themeId: ' + themeId + ' the default theme will be used to draw the titleBarUi icons');
            }
            resizeOutputByDimensions(unscaledWidth, unscaledHeight);
        }

        /**
         * @private
         */
        private function onTitleBarUiClick(e:Event):void
        {
            switch (e.type)
            {
                case Event.OPEN:
                    out.visible = true;
                    break;
                case Event.CLOSE:
                    out.visible = false;
                    break;
                default:
                    logger.warn(this + 'Error, invalid event type: ' + e.type + ' for onTitleBarUiCick');
            }
        }

        /**
         * Clears the console output
         */
        public function clearOutput():void
        {
            out.text = '';
        }

        // BEGIN: Drag and Drop Event Handlers

        /* Creation complete handler for each panel to add the
         * mouseMove event handler to the title bar.
         * Clicking the mouse button, then moving the mouse on the title bar
         * initiates the d&d operation
         */
        private function onThisCreationComplete(event:Event):void
        {
            event.currentTarget.myTitleBar.addEventListener(MouseEvent.MOUSE_DOWN, onMouseMove);
        }

        /* Variables used to hold the mouse pointer's location in the title bar.
         * Since the mouse pointer can be anywhere in the title bar, you have to
         * compensate for it when you drop the panel
         */ /**
         * Variable used to hold the mouse pointer's X location in the title bar.
         * Since the mouse pointer can be anywhere in the title bar, you have to
         * compensate for it when you drop the panel
         *
         * @default No default
         */
        protected var xOff:Number;

        /**
         * Variable used to hold the mouse pointer's Y location in the title bar.
         * Since the mouse pointer can be anywhere in the title bar, you have to
         * compensate for it when you drop the panel
         *
         * @default No default
         */
        protected var yOff:Number;

        /**
         * @private
         *
         * called by the canvas dragEnter event; enables dropping
         */
        private function onDragEnter(event:DragEvent):void
        {
            DragManager.acceptDragDrop(Application(event.target));
        }

        /**
         * @private
         *
         * Drag initiator event handler for the title bar's mouseMove event
         */
        private function onMouseMove(event:MouseEvent):void
        {
            var dragInitiator:Panel = Panel(event.currentTarget.parent);
            var ds:DragSource = new DragSource();
            ds.addData(event.currentTarget.parent, 'panel');
            // Update the xOff and yOff variables to show the
            // current mouse location in the Panel.  
            xOff = event.currentTarget.mouseX;
            yOff = event.currentTarget.mouseY;
            DragManager.doDrag(dragInitiator, ds, event);
        }

        /**
         * @private
         *
         * Function called by the Canvas dragDrop event;
         * Sets the panel's position,
         * "dropping" it in its new location
         */
        private function onDragDrop(event:DragEvent):void
        {
            // Compensate for the mouse pointer's location in the title bar.
            var tempX:int = event.currentTarget.mouseX - xOff;
            event.dragInitiator.x = tempX;
            var tempY:int = event.currentTarget.mouseY - yOff;
            event.dragInitiator.y = tempY;
            // Put the dragged panel on top of all other components.
            flexApplication.setChildIndex(Panel(event.dragInitiator), flexApplication.numChildren - 1);
        }
        // END: Drag and Drop Event Handlers

        // BEGIN: Resizing event handlers.
        protected var resizingPanel:Panel;

        private var initX:Number;

        private var initY:Number;

        /**
         *  Resize area of panel clicked
         */
        protected function onResized(event:MouseEvent):void
        {
            resizingPanel = Panel(event.target);
            initX = event.localX;
            initY = event.localY;
            // Place the guide over the panel. 
            resizeGuide.x = event.target.x;
            resizeGuide.y = event.target.y;
            resizeGuide.height = event.target.height;
            resizeGuide.width = event.target.width;
            // Make sure guide is on top of all other components.
            flexApplication.setChildIndex(resizeGuide, flexApplication.numChildren - 1);
            resizeGuide.visible = true;
            /* Add event handlers so that the SystemManager handles
             * the mouseMove and mouseUp events.
             * Set useCapure flag to true to handle these events
             * during the capture phase so no other component tries to handle them
             */
            systemManager.addEventListener(MouseEvent.MOUSE_MOVE, onSystemMouseMove, true);
            systemManager.addEventListener(MouseEvent.MOUSE_UP, onSystemMouseUp, true);
        }

        /**
         *  Resizes the guide as the user moves the cursor with the mouse key down.
         */
        protected function onSystemMouseMove(event:MouseEvent):void
        {
            event.stopImmediatePropagation();
            resizeGuide.height = resizeGuide.height + event.stageY - initY;
            resizeGuide.width = resizeGuide.width + event.stageX - initX;
            // resize text area along with the panel
            resizeOutput();
            initX = event.stageX;
            initY = event.stageY;
        }

        /**
         * Sizes the panel to the size of the rubber band when the
         * user releases the mouse key.
         * Also removes the event handlers from the SystemManager
         */
        protected function onSystemMouseUp(event:MouseEvent):void
        {
            event.stopImmediatePropagation();
            // Use a minimum panel size of 150 x 75.
            if (resizeGuide.height <= 75)
            {
                resizingPanel.height = 75;
            }
            else
            {
                resizingPanel.height = resizeGuide.height;
            }
            if (resizeGuide.width <= 150)
            {
                resizingPanel.width = 150;
            }
            else
            {
                resizingPanel.width = resizeGuide.width;
            }
            flexApplication.setChildIndex(resizingPanel, flexApplication.numChildren - 1);
            resizeGuide.x = 0;
            resizeGuide.y = 0;
            resizeGuide.height = 0;
            resizeGuide.width = 0;
            resizeGuide.visible = false;
            systemManager.removeEventListener(MouseEvent.MOUSE_MOVE, onSystemMouseMove, true);
            systemManager.removeEventListener(MouseEvent.MOUSE_UP, onSystemMouseUp, true);
        }

        /**
         * @private
         * keeps the output field the perfect size relative to the size of the displayable area in the panel
         */
        private function resizeOutput():void
        {
            var adjustedWidth:Number = resizeGuide.width - (this.borderMetrics.left + this.borderMetrics.right);
            var adjustedHeight:Number = resizeGuide.height - (this.titleBar.height + this.getStyle('verticalGap') + this.getStyle('borderThicknessTop') + this.getStyle('shadowDistance') + 1);
            resizeOutputByDimensions(adjustedWidth, adjustedHeight);
        }

        /**
         * @private
         * keeps the output field the perfect size relative to the size of the displayable area in the panel
         */
        private function resizeOutputByDimensions(w:Number, h:Number):void
        {
            out.width = w - (this.borderMetrics.left + this.borderMetrics.right);
            out.height = h - (this.titleBar.height + this.getStyle('verticalGap') + this.getStyle('borderThicknessTop') + this.getStyle('shadowDistance') + 1);
            out.validateNow();
        }
        // END: Resizing event handlers.
    }
}

